﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace DynaBoard
{
    
    public abstract class UserInputString
    {
        private static Dictionary<Type, int> maxLength =
            new Dictionary<Type, int>();

        private static Dictionary<Type, string> regexPattern =
            new Dictionary<Type, string>();

        private static Dictionary<Type, bool> isRequired =
            new Dictionary<Type, bool>();

        protected string value;

        protected UserInputString(string s, bool trim)
        {
            bool isNull = (s == null);

            if (!isNull && trim)
            {
                s = s.Trim();

                if (s.Length == 0)
                {
                    s = null;
                    isNull = true;
                }
            }

            var type = this.GetType();

            bool required;

            if (UserInputString.TryGetIsRequired(type, out required) && required && isNull)
            {
                throw new ArgumentException
                    ("Is required; cannot be null or zero-length.", "s");
            }

            int len;

            if (UserInputString.TryGetMaxLength(type, out len) && !isNull && s.Length > len)
            {
                throw new ArgumentOutOfRangeException
                    ("s", "Cannot be greather than " + len + " characters in length.");
            }

            string regex;

            if (UserInputString.TryGetRegexPattern(type, out regex) && !isNull && !new Regex(regex).IsMatch(s))
            {
                throw new ArgumentException
                    ("Does not match the RegEx pattern " + regex, "s");
            }

            this.value = s;
        }

        private static bool TryGetAttributeField<TField, TAttribute>
            (Type complexString, Dictionary<Type, TField> cache, out TField value, Func<TAttribute, TField> getField) 
            where TAttribute : Attribute 
        {
            if (cache.TryGetValue(complexString, out value))
            {
                return true;
            }

            var attribute = (from attr in Attribute.GetCustomAttributes(complexString)
                             where attr is TAttribute
                             select attr as TAttribute).SingleOrDefault();

            if (attribute == null)
            {
                return false;
            }

            cache.Add(complexString, (value = getField(attribute)));

            return true;
        }

        private static bool TryGetMaxLength(Type complexString, out int len)
        {
            return UserInputString.TryGetAttributeField<int, MaxLength>
                (complexString, UserInputString.maxLength, out len, attr => attr.Length);
        }

        private static bool TryGetRegexPattern(Type complexString, out string pattern)
        {
            return UserInputString.TryGetAttributeField<string, RegexPattern>
                (complexString, UserInputString.regexPattern, out pattern, attr => attr.Pattern);
        }

        private static bool TryGetIsRequired(Type complexString, out bool required)
        {
            return UserInputString.TryGetAttributeField<bool, IsRequired>
                (complexString, UserInputString.isRequired, out required, attr => attr.Required );
        }

        public static int MaxLength(Type complexString)
        {
            int len;

            if (!UserInputString.TryGetMaxLength(complexString, out len))
            {
                throw new ArgumentException
                    ("Class does not have Maxlength attribute", "complexString");
            }

            return len;
        }

        public static string RegexPattern(Type complexString)
        {
            string regex;

            if (!UserInputString.TryGetRegexPattern(complexString, out regex))
            {
                throw new ArgumentException
                    ("Class does not have RegexPattern attribute", "complexString");
            }

            return regex;
        }

        public static bool IsRequired(Type complexString)
        {
            bool required;

            if (!UserInputString.TryGetIsRequired(complexString, out required))
            {
                throw new ArgumentException
                    ("Class does not have IsRequired attribute", "complexString");
            }

            return required;
        }

        public override string ToString()
        {
            return this.value;
        }
    }

    [AttributeUsage(AttributeTargets.Class)]
    public class IsRequired : Attribute
    {
        public readonly bool Required;

        public IsRequired(bool required)
        {
            this.Required = required;
        }
    }

    [AttributeUsage(AttributeTargets.Class)]
    public class MaxLength : Attribute
    {
        public readonly int Length;

        public MaxLength(uint len)
        {
            this.Length = Convert.ToInt32(len);

            if (this.Length == 0)
            {
                throw new ArgumentOutOfRangeException
                    ("len", "Must be greater than 0.");
            }
        }
    }

    [AttributeUsage(AttributeTargets.Class)]
    public class RegexPattern : Attribute
    {
        public static string PrintableChars = "[^\x00-\x1f]";
        public static string EmailAddress = @"^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$";

        public readonly string Pattern;

        public RegexPattern(string pattern)
        {
            if (string.IsNullOrEmpty(pattern))
            {
                throw new ArgumentException
                    ("Cannot be null or  empty", "pattern");
            }

            this.Pattern = pattern;
        }

        public static string WithLength(string pattern, int minLength, int maxLength)
        {
            return pattern + string.Format("{{0},{1}}", minLength, maxLength);
        }
    }

}
